package jass.generators;
import jass.engine.*;
import java.io.*;
import java.util.*;

/** Vibration model of object, capable of playing sound.
    @author Kees van den Doel (kvdoel@cs.ubc.ca)
*/
public class ModalObject extends InOut {
    /** Sampling rate in Hertz. */
    public float srate;

    /** Modal data. */    
    public ModalModel modalModel;

    /** Associate a Source with a ModalObject.Contact. */
    private Hashtable<Source,Contact> source_contact = new Hashtable<Source,Contact>();
    
    /** Represents contact with location in barycentric coordinates. */
    public class Contact {

        /** State of contact */
        public boolean isOn = false;

        /** Current barycentric location points. */
        public int p1=0,p2=0,p3=0;
	
        /** Current barycentric coordinates of location. */
        public float b1=1,b2=0,b3=0;

        /** Reson filter gain vector. */
        public float[] ampR = null; 

        /** Constructor, allocates nmodes.
            @param af audio force of Contact.
            @param nf number of modes.
        */
        public Contact() {
            ampR = new float[modalModel.nf];
        }
	
        /** Turn on. */
        public void start() {
            isOn = true;
        }

        /** Turn off. */
        public void stop() {
            isOn = false;
        }

        /** Compute the gain coefficients  from the modal model parameters at point p,
            given inside triangle of point p1,p2,p3, with barycentric coordinated b1,b2,b3
            @param p1 location index 1.
            @param p2 location index 2.
            @param p3 location index 3.
            @param b1 barycentric coordinate 1.
            @param b2 barycentric coordinate 2.
            @param b3 barycentric coordinate 3.
        */
        public void setLocation(int p1, int p2, int p3, float b1, float b2, float b3) {
            this.p1 = p1;
            this.p2 = p2;
            this.p3 = p3;
            this.b1 = b1;
            this.b2 = b2;
            this.b3 = b3;
            computeLocation();
        }

        /** Compute gains of contact
         */
        public void computeLocation() {
            for(int i=0;i<modalModel.nf;i++) {
                ampR[i] = modalModel.ascale * c_i[i] *
                    (b1* modalModel.a[p1][i] + b2* modalModel.a[p2][i] + b3* modalModel.a[p3][i]);
            }   
        }
    };

    /** Used a a temp buffer in inner loop to store array of gains for all contacts. */
    private float[] contactAmpTemp;

    /** Location vector of Contact objects. */
    Vector<Contact> contactVector = new Vector<Contact>();

    /** State of filters. */
    private float [] yt_1, yt_2; 
    
    /** The transfer function of a reson filter is H(z) = 1/(1-twoRCosTheta/z + R2/z*z). */
    private float[] R2;

    /** The transfer function of a reson filter is H(z) = 1/(1-twoRCosTheta/z + R2/z*z). */
    private float[] twoRCosTheta;

    /** Cached values. */
    private float[] c_i;

    /** Add a Source. Implements Sink interface. Must take into account that may add
        source while running, so have to set time.
        @param s Source to add.
    */    
    public synchronized Object addSource(Source s) throws SinkIsFullException {
        sourceContainer.addElement(s);
        s.setTime(getTime());
        Contact c = new Contact();
        contactVector.addElement(c);
        c.computeLocation();
        source_contact.put(s,c);
        // allocate temp storage associated with this source
        contactAmpTemp = new float[contactVector.size()];
        return c;
    }
    
    /** Remove a Source.  Implements Sink interface.
        @param s Source to remove.
    */
    public synchronized void removeSource(Source s) {
        sourceContainer.removeElement(s);
        //System.out.println("ncontacts="+contactVector.size());
        Contact c = source_contact.get(s);
        source_contact.remove(s);
        //System.out.println("removeSource("+s+"), contact="+c);
        contactVector.removeElement(c);
        //System.out.println("ncontacts="+contactVector.size());
    }

  
    /** Scale dampings.
        @param d damping scale. 
    */
    public void setDamping(float dscale) {
        modalModel.dscale = dscale;
        computeFilter();
    }

    /** Scale frequencies.
        @param fscale frequency scale. 
    */
    public void setFrequencyScale(float fscale) {
        modalModel.fscale = fscale;
        computeFilter();
    }

    /** Create and initialize, but don't set any modal parameters.
        @param srate sampling rate in Hertz.
        @param nf number of modes.
        @param np number of locations.
        @param bufferSize Buffer size used for real-time rendering.
    */
    public ModalObject(float srate,int nf,int np,int bufferSize) {
        super(bufferSize);
        this.srate = srate;
        modalModel = new ModalModel(nf,np);
        allocate(nf,np);
    }

    /** Create and initialize with provided modal data.
        @param m modal model to load.
        @param srate sampling rate in Hertz.
        @param bufferSize Buffer size used for real-time rendering.
    */
    public ModalObject(ModalModel m,float srate,int bufferSize) {
        super(bufferSize);
        this.srate = srate;
        modalModel = m;
        allocate(modalModel.nf,modalModel.np);
        computeFilter();
    }

    /** Reduce number of modes used.
        @param nf number of modes to use.
    */
    public void setNf(int nf) {
        if(nf < modalModel.nf) {
            modalModel.nfUsed = nf;
        }
    }

    /** Allocate data.
        @param nf number of modes.
        @param np number of locations.
    */
    private void allocate(int nf,int np) {
        R2 = new float[nf];
        twoRCosTheta = new float[nf];
        yt_1 = new float[nf];
        yt_2 = new float[nf];
        c_i = new float[nf];
        clearHistory();
    }
    
    /** Compute the filter coefficients used for real-time rendering
        from the modal model parameters. 
    */
    public void computeFilter() {
        computeResonCoeff();
        for(int i=0;i<contactVector.size();i++ ){
            contactVector.elementAt(i).computeLocation();
        }
    }

    /** Compute the reson coefficients from the modal model parameters.
        Cache values for location computation.
    */
    public void computeResonCoeff() {
        for(int i=0;i<modalModel.nf;i++) {
            float tmp_r = (float)(Math.exp(-modalModel.dscale*modalModel.d[i]/srate));
            R2[i] = tmp_r*tmp_r;
            twoRCosTheta[i] = (float)(2*Math.cos(2*Math.PI*modalModel.fscale*modalModel.f[i]/srate)*tmp_r);
            c_i[i] = (float)(Math.sin(2*Math.PI*modalModel.fscale*modalModel.f[i]/srate)*tmp_r);
        }        
    }
    
    /** Set state to non-vibrating.
     */
    public void clearHistory() {
        for(int i=0;i<modalModel.nf;i++) {
            yt_1[i] = yt_2[i] = 0;
        }
    }

    /** Compute the next buffer and store in member float[] buf.
     */
    protected synchronized void computeBuffer() {
        try {
            computeModalFilterBank(this.buf);
        } catch(BufferNotAvailableException e) {
            System.out.println(e);
        }
    }

    /** Apply external force[] and compute response through bank of modal filters.
        @param output provided output buffer.
    */
    private void computeModalFilterBank(float[] output) throws BufferNotAvailableException {
        int bufsz = getBufferSize();
        for(int k=0;k<bufsz;k++) {
            output[k] = 0;
        }
        int nf = modalModel.nfUsed;
        int ncontacts = contactVector.size();

        for(int i=0;i<nf;i++) {
            float tmp_twoRCosTheta = twoRCosTheta[i];
            float tmp_R2 = R2[i];
            float tmp_yt_1 = yt_1[i];
            float tmp_yt_2 = yt_2[i];
            for(int ic=0;ic<ncontacts;ic++) {
                // move array access out of inner loop
                contactAmpTemp[ic] = contactVector.elementAt(ic).ampR[i]; 
            }
            for(int k=0;k<bufsz;k++) {
                float ynew = tmp_twoRCosTheta * tmp_yt_1 - tmp_R2 * tmp_yt_2;
                // Just this loop (empty) brings 668 (one contact) down to 544
                for(int ic=0;ic<ncontacts;ic++) {
                    // stuff in this loop slows things a factor of 3, to 200, with everything

                    // Just this line by itself gives 266
                    Contact contact = contactVector.elementAt(ic);

                    // Just this stuff gives 210
                    if(contact.isOn) {
                        // BUG: This assumes scrBuffers has Sources in same order as Contacts. Probably OK but check!
                        ynew +=  contactAmpTemp[ic] * srcBuffers[ic][k];
                    }
                    // try just array mult and comment out the above gives 219 => arrays are very very bad!
                    //ynew += contactAmpTemp[ic] * contactAmpTemp[ic];
                    //ynew += foo[ic] * foo[ic]; // no, this makes no difference with above line!
                }
                
                tmp_yt_2 = tmp_yt_1;
                tmp_yt_1 = ynew;
                output[k] += ynew;
            }
            yt_1[i] = tmp_yt_1;
            yt_2[i] = tmp_yt_2;
        }
    }

}
